﻿using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Text;
using System.Web;

using DocumentServices.Storage;

namespace DocumentServices.RequestHandlers
{
    /// <summary>
    /// Returns the requested package part from the XPS document.
    /// </summary>
    public class GetDocumentPartHandler
        : IRequestHandler
    {
        private static bool IsModified(HttpContext context, DateTime lastModified)
        {
            string value = context.Request.Headers["If-Modified-Since"];
            if (value != null) {
                DateTime ifModifiedSince;
                if (DateTime.TryParse(value, out ifModifiedSince)) {
                    // ensures milliseconds and ticks are not taken into account (needed when determining HTTP if not modified since)
                    lastModified = new DateTime(lastModified.Year, lastModified.Month, lastModified.Day, lastModified.Hour, lastModified.Minute, lastModified.Second);

                    return ifModifiedSince != lastModified;
                }
            }
            return true;
        }

        private static void WriteToResponse(HttpContext context, DateTime lastModified, string contentType, Stream stream)
        {
            // ensures lastmodified does not exceed now
            if (lastModified > DateTime.Now) {
                lastModified = DateTime.Now;
            }

            context.Response.ContentType = contentType;
            context.Response.Cache.SetLastModified(lastModified);
            context.Response.Cache.SetExpires(DateTime.UtcNow.AddDays(1));      // expires in 24 hours

            // compress output (omit for images)
            if (!contentType.StartsWith("image/")) {
                var acceptEncoding = context.Request.Headers["Accept-Encoding"];
                if (acceptEncoding != null) {
                    if (acceptEncoding.Contains("gzip")) {
                        context.Response.Filter = new GZipStream(context.Response.Filter, CompressionMode.Compress);
                        context.Response.AppendHeader("Content-Encoding", "gzip");

                    }
                    else if (acceptEncoding.Contains("deflate")) {
                        context.Response.Filter = new DeflateStream(context.Response.Filter, CompressionMode.Compress);
                        context.Response.AppendHeader("Content-Encoding", "deflate");
                    }
                }
            }

            byte[] buffer = new byte[2 << 14];    // write blocks of 32768 bytes
            int read;
            while ((read = stream.Read(buffer, 0, buffer.Length)) > 0) {
                context.Response.OutputStream.Write(buffer, 0, read);
            }
        }

        /// <summary>
        /// Handles the request.
        /// </summary>
        /// <param name="context">The context.</param>
        /// <param name="store">The store.</param>
        public void HandleRequest(HttpContext context, IDocumentStore store)
        {
            var query = HttpUtility.UrlDecode(context.Request.QueryString.ToString());
            var index = query.IndexOf('/');
            if (index == -1) {
                throw new ArgumentException("Invalid request");
            }
            var name = query.Substring(0, index);
            var partName = query.Substring(index);

            if (store.ContainsDocument(name)) {
                var lastModified = store.GetDocumentLastModified(name);

                if (!IsModified(context, lastModified)) {
                    // return 304 not modified
                    context.Response.StatusCode = (int)HttpStatusCode.NotModified;
                    context.Response.StatusDescription = "Not Modified";
                }
                else {
                    using (var document = store.OpenDocument(name)) {
                        var part = store.GetDocumentPart(document, partName);
                        if (part != null) {
                            using (part.Stream) {
                                WriteToResponse(context, lastModified, part.ContentType, part.Stream);
                            }
                        }
                        else {
                            // part not found
                            context.Response.StatusCode = (int)HttpStatusCode.NotFound;
                        }
                    }
                }
            }
            else {
                // document not found
                context.Response.StatusCode = (int)HttpStatusCode.NotFound;
            }
        }
    }
}
